package com.itbaizhan.myshiro.controller;

import com.itbaizhan.myshiro.domain.Users;
import com.itbaizhan.myshiro.service.UserService;
import com.sun.org.omg.CORBA.InitializerSeqHelper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.sql.DataSource;
/*调用subject接口进行认证授权，subject是shiro中的一个接口
* subject使用SecurityManager进行授权*/

@Controller
public class LoginController {

    /*shiro入门,使用配置文件shiro.ini,没有使用数据库*/
    @RequestMapping("/user/login1")
    //从前端传入username与password
    public String login(String username,String password){
        //1获取SecurityManager工厂，读取配置文件（这方法落后，后期连接数据库可以直接创建securityManager）
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2获取securityManager对象
        SecurityManager securityManager = factory.getInstance();
        //3将securityManager对象高置到运行环境中，因为在容器中没有这对象就没有办法运行
        SecurityUtils.setSecurityManager(securityManager);
        //4获取subject对象  要使用它的认证方法
        Subject subject = SecurityUtils.getSubject();
        //5将前端传来的用户名密码封装为shiro提供的身份对象
        // 因为subject要认证，而认证必须是shiro的身份对象
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            //6,shiro认证  将前端传来的与在shiro.ini文件的比较
            subject.login(token);
            //认证通过跳转到主页面
            return "main";
        }catch (AuthenticationException e) {
            //认证不通过跳转到失败的页面    AuthenticationException  认证失败的异常
            System.out.println("账户失效");
            return "fail";
        }
    }

    /*Realm
领域。他是连接数据源+认证功能+授权功能的具体实现。SecurityManager通过Realm获取用户的身份和权限信息，并对用户进行认证和授权。*/
    //连接数据库案例    将数据库放进jdbcRealm ，再将jdbcRealm放进securityManager
    // 数据库的用户名密码封装为shiro对象  再与subject中的securityManager 作比较是不相同
    /*@Autowired
    private DataSource dataSource;  //在application.yml配置了数据源
    @RequestMapping("/user/login")
    public String login2(String username,String password) {
        //1获取securityManager对象
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
       //2,为securityManager对象设置Realm
        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(dataSource);
        securityManager.setRealm(jdbcRealm);

        //3将securityManager对象高置到运行环境中  因为在容器中没有这对象就没有办法运行
        SecurityUtils.setSecurityManager(securityManager);
        //4获取subject对象  要使用它的认证方法
        Subject subject = SecurityUtils.getSubject();
        //5将前端传来的用户名密码封装为shiro提供的身份对象
        // 因为subject要认证，而认证必须是shiro的身份对象
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            //6,shiro认证  将前端传来的与在shiro.ini文件的比较
            subject.login(token);
            //认证通过跳转到主页面
            return "main";
        } catch (AuthenticationException e) {
            //认证不通过跳转到失败的页面    AuthenticationException  认证失败的异常
            return "fail";
        }
    }*/

    /*在ShiroConfig类把securityManager与JdbcRealm放进spring容器中进行管理
    * 在UserService类进行逻辑认证
    * 在controller类进行认证*/
    @Autowired
    private UserService userService;
    @RequestMapping("/user/login")
    public String login3(String username,String password,String rememberMe){
        try {
            //把参数给userLogin方法进行认证
            userService.userLogin(username, password,rememberMe);
            return "main";
        }catch (DisabledAccountException e){
            //认证不通过跳转到失败的页面    AuthenticationException  认证失败的异常
            System.out.println("账户失效");
            return "fail";
        }catch (ConcurrentAccessException e){
            System.out.println("竞争次数过多");
            return "fail";
        }catch (ExcessiveAttemptsException e){
            System.out.println("尝试次数过多");
            return "fail";
        }catch (UnknownAccountException e){
            System.out.println("账户不正确");
            return "fail";
        }catch (IncorrectCredentialsException e){
            System.out.println("密码不正确");
            return "fail";
        }catch (ExpiredCredentialsException e){
            System.out.println("凭证过期");
           return "fail";
        }
    }

    //Shiro认证_获取认证数据   subject.getPrincipal()
    // Principal:最重要的; 主要的;
//n.大学校长; 学院院长; 本金; 资本; 主要演员; 主角; 当事人，委托人;
    //登录后再访问地址：localhost:80/user/getUsername
    /*用户认证通过后，有时我们需要获取用户信息，比如在网站顶部显示：
    欢迎您，XXX。获取用户信息的写法如下：*/
    @RequestMapping("/user/getUsername")
    @ResponseBody
    public String getUsername(){
        //获取subject对象
        Subject subject = SecurityUtils.getSubject();
        //获取用户   因为知道在登录时会封装成users对象，所以可以强转
        /*登录时会封装：
        return new SimpleAuthenticationInfo(admin,
                admin.getPassword(),
                ByteSource.Util.bytes(admin.getSalt()),
                "MyRealm2");*/
       Users users =  (Users)subject.getPrincipal();
       return users.getUsername();

    }

    //退出登录
    @RequestMapping("/user/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        //退出后重定向到登录页面
        return "redirect:/login";

    }

    //支付页面
    @RequestMapping("/user/pay")
    @ResponseBody //页面不跳转
    public String pay(){
        return "支付功能";
    }
}

/*如果报错 dataSource count not be find
加依赖 spring jdbc
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>*/

/*
数据库里的表名必须叫users 用户名必须是username 密码必须是password  因为在
Realm负责连接数据源并进行具体认证，它有一个子类JdbcRealm，该类可以自动连接数据库认证。
而JdbcRealm 定义好的sql语句，表名是users  ,用户名username,密码password
CREATE TABLE `users` (
 `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
 `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `users` VALUES ('baizhan', 'baizhan');*/

/*当Shiro认证失败后，会抛出AuthorizationException异常。该异常的子类分别代表不同的认证失败原因，
我们可以通过捕捉它们确定认证失败原因。

DisabledAccountException：账户失效
ConcurrentAccessException：竞争次数过多（同时有多名用户登录）
ExcessiveAttemptsException：尝试次数过多
UnknownAccountException：用户名不正确
IncorrectCredentialsException：凭证（密码）不正确
ExpiredCredentialsException：凭证过期
我们一般在Controller中处理认证异常

注：

如果将异常信息提示给用户，尽量把异常信息表示的婉转一些。
比如不管用户名还是密码错误，都提示用户名或密码错误，
这样有助于提升代码的安全性。*/